/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE80;
using EnvDTE;
using VSLangProj80;
using VSLangProj;
using DaveSexton.DocProject.Engine;
using System.Diagnostics;
using System.Windows.Forms;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject
{
	internal sealed class IdeEventListener : System.ComponentModel.Component
	{
		#region Public Properties
		#endregion

		#region Private / Protected
		private readonly object sync = new object();
		private readonly SolutionEvents solutionEvents;
		private readonly BuildEvents buildEvents;
		private readonly CommandEvents buildCancelEvents, fileSaveEvents, fileSaveAllEvents;
		private volatile bool disposed;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="IdeEventListener" /> class.
		/// </summary>
		internal IdeEventListener()
		{
			// Cache event classes so that event handlers aren't garbage collected early.
			solutionEvents = VSEnvironmentHost.DTE.Events.SolutionEvents;
			solutionEvents.Opened += SolutionOpened;
			solutionEvents.AfterClosing += SolutionClosed;
			solutionEvents.ProjectAdded += ProjectAdded;
			solutionEvents.ProjectRemoved += ProjectRemoved;
			solutionEvents.ProjectRenamed += ProjectRenamed;

			// Build command group; Cancel command
			buildCancelEvents = VSEnvironmentHost.DTE.Events.get_CommandEvents("{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 890);
			buildCancelEvents.AfterExecute += BuildCancelCommand;

			// File command group; SaveSelectedItems command
			fileSaveEvents = VSEnvironmentHost.DTE.Events.get_CommandEvents("{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 331);
			fileSaveEvents.BeforeExecute += FileSaveCommand;

			// File command group; SaveAll command
			fileSaveAllEvents = VSEnvironmentHost.DTE.Events.get_CommandEvents("{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 224);
			fileSaveAllEvents.BeforeExecute += FileSaveAllCommand;

			buildEvents = VSEnvironmentHost.DTE.Events.BuildEvents;
			buildEvents.OnBuildProjConfigDone += OnBuildProjConfigDone;
		}
		#endregion

		#region Methods
		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand,
			Flags = System.Security.Permissions.SecurityPermissionFlag.Infrastructure)]
		public override object InitializeLifetimeService()
		{
			return null;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void SaveProject(IDocProject docProject)
		{
			string projectName = null;

			try
			{
				if (docProject != null)
				{
					projectName = docProject.Name;
					((VSEnvironmentHost) DocProjectEnvironment.Host).OnProjectSaved(new DocProjectEventArgs(docProject));
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorSavingProject, projectName);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorSavingProject, projectName);
			}
		}

		protected override void Dispose(bool disposing)
		{
			try
			{
				if (disposing && !disposed)
				{
					lock (sync)
					{
						if (!disposed)
						{
							try
							{
								solutionEvents.Opened -= SolutionOpened;
								solutionEvents.AfterClosing -= SolutionClosed;
								solutionEvents.ProjectAdded -= ProjectAdded;
								solutionEvents.ProjectRemoved -= ProjectRemoved;
								solutionEvents.ProjectRenamed -= ProjectRenamed;

								buildCancelEvents.AfterExecute -= BuildCancelCommand;
								fileSaveEvents.BeforeExecute -= FileSaveCommand;
								fileSaveAllEvents.BeforeExecute -= FileSaveAllCommand;

								buildEvents.OnBuildProjConfigDone -= OnBuildProjConfigDone;
							}
							catch (System.Runtime.InteropServices.InvalidComObjectException) { }

							disposed = true;
						}
					}
				}
			}
			finally
			{
				base.Dispose(disposing);
			}
		}
		#endregion

		#region Event Handlers
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void SolutionOpened()
		{
			try
			{
				Debug.WriteLine("Solution Opened", "IdeEventListener");

				foreach (VSAnyProject project in DocProjectEnvironment.FindProjects(null))
				{
					IDocProject docProject = project as IDocProject;

					if (docProject != null)
					{
						try
						{
							((VSEnvironmentHost) DocProjectEnvironment.Host).OnProjectOpened(new DocProjectEventArgs(docProject));
						}
						catch (Exception ex)
						{
							Log.Exception(ex, Resources.Text.ErrorOpeningProject, docProject.Name);
							DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorOpeningProject, docProject.Name);
						}
						finally
						{
							// ensure that the project has an engine created and that the first build will be a Full build
							BuildController.GetEngineForProject(docProject).BuildType = BuildType.Full;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorOpeningSolution);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorOpeningSolution);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void SolutionClosed()
		{
			try
			{
				Debug.WriteLine("Solution Closed", "IdeEventListener");

				try
				{
					foreach (IDocProject docProject in DocProjectEnvironment.Projects)
					{
						try
						{
							((VSEnvironmentHost) DocProjectEnvironment.Host).OnProjectClosed(new DocProjectEventArgs(docProject));
						}
						catch (Exception ex)
						{
							Log.Exception(ex, Resources.Text.ErrorClosingProject, docProject.Name);
							DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorClosingProject, docProject.Name);
						}
					}
				}
				finally
				{
					BuildController.ClearEngines();
					DocProjectEnvironment.ClearProjects();
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorClosingSolution);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorClosingSolution);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void ProjectAdded(Project project)
		{
			try
			{
				IDocProject docProject = DocProjectEnvironment.GetDocProject(project);

				if (docProject != null)
				{
					try
					{
						((VSEnvironmentHost) DocProjectEnvironment.Host).OnProjectOpened(new DocProjectEventArgs(docProject));
					}
					catch (Exception ex)
					{
						Log.Exception(ex, Resources.Text.ErrorOpeningProject, docProject.Name);
						DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorOpeningProject, docProject.Name);
					}
					finally
					{
						// ensure that the project has an engine created and that the first build will be a Full build
						BuildController.GetEngineForProject(docProject).BuildType = BuildType.Full;
					}
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorAddingProject, project.Name);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorAddingProject, project.Name);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void ProjectRemoved(Project project)
		{
			try
			{
				IDocProject docProject = DocProjectEnvironment.GetDocProject(project);

				if (docProject != null)
				{
					try
					{
						DocProjectEnvironment.RemoveProject(docProject);
						BuildController.RemoveEngineForProject(docProject);
					}
					finally
					{
						((VSEnvironmentHost) DocProjectEnvironment.Host).OnProjectClosed(new DocProjectEventArgs(docProject));
					}
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorRemovingProject, project.Name);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorRemovingProject, project.Name);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void ProjectRenamed(Project project, string oldName)
		{
			try
			{
				IDocProject docProject = DocProjectEnvironment.GetDocProject(project);

				if (docProject != null)
				{
					if (BuildController.NotifyNameChanged(docProject))
						Debug.WriteLine("DocProject renamed: " + docProject.Name, "IdeEventListener");
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorRenamingProject, project.Name);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorRenamingProject, project.Name);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void OnBuildProjConfigDone(string projectName, string projectConfig, string platform, string solutionConfig, bool success)
		{
			IDocProject docProject = null;

			try
			{
				docProject = DocProjectEnvironment.FindProject(projectName) as IDocProject;

				if (docProject != null)
				{
					if (success)
						BuildController.SyncProjectItems(docProject);

					((VSEnvironmentHost) DocProjectEnvironment.Host).OnProjectBuildComplete(new DocProjectEventArgs(docProject));
				}
			}
			catch (Exception ex)
			{
				try
				{
					Debug.WriteLine(ex.ToString());

					Log.Exception(ex, Text.BuildOutputVerificationError, projectName);
				}
				finally
				{
					DocProjectEnvironment.ShowErrorDialog(Errors.DocProjectOutputVerficationError, Environment.NewLine, ex.Message);
				}
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void BuildCancelCommand(string Guid, int ID, object CustomIn, object CustomOut)
		{
			try
			{
				BuildController.Cancel();
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorCancelingBuild);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorCancelingBuild);
			}
		}

		private void FileSaveCommand(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
		{
			if (DocProjectPropertiesToolWindow.Instance.HasFocus)
				SaveProject(DocProjectEnvironment.SelectedProject);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void FileSaveAllCommand(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
		{
			try
			{
				foreach (IDocProject docProject in DocProjectEnvironment.Projects)
				{
					SaveProject(docProject);
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.ErrorSavingAllProjects);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.ErrorSavingAllProjects);
			}
		}
		#endregion
	}
}
